#region using

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

#endregion

namespace CodeDom
{
  public class Template : ItemProxy
  {
    private static readonly Guid TEMPLATE_TEMPLATEID = new Guid("{AB86861A-6030-46C5-B394-E8F99E8B87DB}");
    [ThreadStatic] private static Stack<Guid> _inheritedTemplateStack = new Stack<Guid>();
    private readonly Guid TEMPLATE_BASETEMPLATE_FIELDID = new Guid("{12C33F3F-86C5-43A5-AEB4-5598CEC45116}");
    private IEnumerable<Guid> _baseTemplateIDs;
    private IEnumerable<Template> _baseTemplates;
    private IEnumerable<Template> _inheritedBaseTemplates;
    private IEnumerable<TemplateField> _inheritedTemplateFields;
    private IEnumerable<TemplateField> _templateFields;

    internal Template(Item item) : base(item)
    {
    }

    public IEnumerable<TemplateField> TemplateFields
    {
      get
      {
        if (_templateFields == null)
        {
          _templateFields = Children.Where(TemplateSection.IsValid)
            .SelectMany(i => i.Children)
            .Where(TemplateField.IsValid)
            .Select(i => new TemplateField(i));
        }
        return _templateFields;
      }
    }


    /// <summary>
    /// Returns the IDs of the immediate basetemplates for the template
    /// </summary>
    public IEnumerable<Guid> BaseTemplatesIDs
    {
      get
      {
        if (_baseTemplateIDs == null)
        {
          Field baseTemplateField = GetField(TEMPLATE_BASETEMPLATE_FIELDID);
          _baseTemplateIDs = new Collection<Guid>();
          if (Item.Connection.DerivesFromStandardTemplate(ID))
            ((Collection<Guid>) _baseTemplateIDs).Add(Item.Connection.StandardTemplate.ID);

          if (baseTemplateField != null)
            _baseTemplateIDs = baseTemplateField.Value
              .Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
              .Select(id => new Guid(id))
              .Union(_baseTemplateIDs)
              .Where(id => id != ID)
              .Distinct();
        }
        return _baseTemplateIDs;
      }
    }

    /// <summary>
    /// Returns all basetemplates for the template
    /// </summary>
    /// <value>All base templates.</value>
    public IEnumerable<Template> GetBaseTemplates(bool includeInherited)
    {
      if (!includeInherited)
      {
        InitializeImmediateBaseTemplates();
        return _baseTemplates;
      }

      InitializeInheritedBaseTemplates();
      return _inheritedBaseTemplates;
    }

    private void InitializeInheritedBaseTemplates()
    {
      if (_inheritedBaseTemplates == null)
      {
        Dictionary<Guid, Template> templates = new Dictionary<Guid, Template>();
        AddBaseTemplates(this, templates);
        _inheritedBaseTemplates = templates.Values.Where(t => !IsBaseTemplateOfAny(t, templates.Values));
      }
    }

    private bool IsBaseTemplateOfAny(Template template, IEnumerable<Template> templates)
    {
      IEnumerable<Template> otherTemplates = templates.Where(t => t.ID != template.ID);
      return otherTemplates.Any(t => t.DerivesFrom(template));
    }

    private void InitializeImmediateBaseTemplates()
    {
      if (_baseTemplates != null)
        return;

      Collection<Template> templates = new Collection<Template>();
      foreach (Guid id in BaseTemplatesIDs)
      {
        if (id == Guid.Empty)
          continue;

        GetTemplateAndAddToCollection(id, templates);
      }
      _baseTemplates = templates.Where(t => !IsBaseTemplateOfAny(t, templates));
    }

    private void GetTemplateAndAddToCollection(Guid id, ICollection<Template> templates)
    {
      //Cannot derive from self
      if (id == ID)
        return;
      Template templateToAdd = Item.Connection.GetTemplate(id);
      if (templateToAdd == null)
        return;
      //Check if the template already has been added
      if (templates.Contains(templateToAdd, new TemplateEqualityComparer()))
        return;
      //Check if the templateToAdd is already the base template of any of the previously added
      if (templates.Any(t => t.DerivesFrom(templateToAdd)))
        return;
      templates.Add(templateToAdd);
    }

    private void AddBaseTemplates(Template template, IDictionary<Guid, Template> addTo)
    {
      //Avoid stack overflow
      if (_inheritedTemplateStack.Contains(ID))
        return;

      _inheritedTemplateStack.Push(template.ID);
      try
      {
        foreach (Template baseTemplate in template.GetBaseTemplates(false).Where(t => !addTo.ContainsKey(t.ID)))
        {
          addTo.Add(baseTemplate.ID, baseTemplate);
          AddBaseTemplates(baseTemplate, addTo);
        }
      }
      finally
      {
        _inheritedTemplateStack.Pop();
      }
    }

    /// <summary>
    /// Returns the template fields.
    /// </summary>
    /// <param name="includeInherited">If true, include fields from all base templates also</param>
    /// <returns></returns>
    public IEnumerable<TemplateField> GetTemplateFields(bool includeInherited, bool includeStandardFields)
    {
      var returnValue = TemplateFields;
      if (includeInherited)
      {
        if (_inheritedTemplateFields == null)
        {
          IEnumerable<TemplateField> baseTemplateFields = GetBaseTemplates(true).SelectMany(t => t.GetTemplateFields(true, true));
          _inheritedTemplateFields = TemplateFields.Distinct(new TemplateFieldEqualityComparer());
        }
        returnValue = returnValue.Union(_inheritedTemplateFields).Distinct(new TemplateFieldEqualityComparer());
      }

      if (!includeStandardFields)
      {
        if (ID == Item.Connection.StandardTemplate.ID)
          return new Collection<TemplateField>();
        returnValue = returnValue.Except(Item.Connection.StandardTemplate.GetTemplateFields(true, true));
      }
      return returnValue;
    }

    /// <summary>
    /// Gets a template field by name.
    /// </summary>
    /// <param name="name">The name.</param>
    /// <returns>The first field which matches the name, or null if the fields does not exist</returns>
    public TemplateField GetTemplateField(string name)
    {
      return TemplateFields.First(f => String.Equals(f.Name, name, StringComparison.InvariantCultureIgnoreCase));
    }

    /// <summary>
    /// Gets a template field by id.
    /// </summary>
    /// <param name="id">The id.</param>
    /// <returns>The field with the given ID, or null if the fields does not exist</returns>
    public TemplateField GetTemplateField(Guid id)
    {
      return TemplateFields.First(f => f.ID == id);
    }

    /// <summary>
    /// Determines whether the specified element is a valid template
    /// </summary>
    /// <returns>
    /// 	<c>true</c> if the specified element is valid; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsValid(IItem item)
    {
      return (item.TemplateID == TEMPLATE_TEMPLATEID);
    }

    private bool DerivesFrom(Template template)
    {
      return GetBaseTemplates(true).Contains(template, new TemplateEqualityComparer());
    }

  }
}